/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad 
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 */

package com.conradroche.matra.decl;

import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import com.conradroche.dtd.decl.AttDef;
import com.conradroche.matra.data.*;
import com.conradroche.matra.exception.DTDSyntaxException;

/**
 * Class to store the attribute list for an element type.
 * @author Conrad Roche
 */
public class AttList {
	
	/**
	 * The name of the Element type to which this
	 * attribute list is associated.
	 */
	private String eleName;
	
	/**
	 * The collection of attributes for the Element type.
	 */
	private Vector attribs;

	/**
	 * Is <code>true</code> if any of the attributes 
	 * is of type "ID". 
	 */
	private boolean hasIDAttribute = false;
	
	/**
	 * Is <code>true</code> if any of the attributes 
	 * is of type "NOTATION". 
	 */
	private boolean hasNotationAttribute = false;
	
/**
 * AttList default constructor.
 */
public AttList() {
	super();
	reset();
}

/**
 * Read an attribute list declaration from the string.
 * 
 * @param content The text defining the attribute list.
 * 
 * @throws DTDSyntaxException If syntax error encountered on parsing.
 */
public AttList(String content) throws DTDSyntaxException {

	parseAttList(content);
}

/**
 * Parses the specified content which defines an 
 * attribute list and populate the current AttList object.
 * 
 * Parses the content based on the following grammar -
 * <code>
 * [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
 * [53] AttDef ::= S Name S AttType S DefaultDecl
 * </code>
 * 
 * @param content The text defining the attribute list.
 * 
 * @throws DTDSyntaxException If a syntax error is encountered.
 */
private void parseAttList(String content) throws DTDSyntaxException {

	//CR: TODO: Make this a public static method that returns a AttList object
	//CR: TODO: Maybe this and the remaining parsing methods should move to a new class - make these lightwieght class.
	
	reset();
	if(content == null || content.length() == 0) {
		//CR: NOTE: hmm. Is this desired or an Exception?
		return;
	}

	DTDData attrCont = new DTDData(content.trim());

	String eleName = attrCont.getNextToken();
	//setEleName(eleName);

	//System.out.println("eleName = '" + eleName + "', AttDef = '" + AttDef + "'");
	setEleName(eleName);

	if(attrCont.getRemaining() != null) {
		//CR: TODO: IS this wasteful creation of another DTDData
		//object necessary? Don't think so ...
		DTDData AttDef = new DTDData(attrCont.getRemaining().trim());

		while( !AttDef.endOfData() ) {
			Attribute attr = new Attribute();
			attr.readNextAttribute(AttDef);
			addAttribute(attr);
		}
	}
}

/**
 * Add an attribute to the attribute list.
 * 
 * @param a The Attribute to be added.
 */
public void addAttribute(Attribute a) {
	
	/*
	 *  3.3 Attribute-List Declarations [XML Rec 1.0]
	 * 
	 * When more than one definition is provided for the same attribute 
	 * of a given element type, the first declaration is binding and 
	 * later declarations are ignored.
	 */
	
	//check if its already defined
	if(!attribs.contains(a)) {
		
		/*
		 * 3.3.1 Attribute Types [XML Rec 1.0]
		 * 
		 * Validity constraint: One ID per Element Type
		 * 		No element type may have more than one ID attribute specified. 
		 */
		if(a.isIDType()) {
			if(hasIDAttribute) {
				//CR: TODO: Throw error here!
			}
			hasIDAttribute = true;
		}
		
		/*
		 * 3.3.1 Attribute Types [XML Rec 1.0]
		 * 
		 * No element type may have more than one 
		 * NOTATION attribute specified.
		 */
		if(a.getAttType() == Attribute.TYPE_NOTATION) {
			if(hasNotationAttribute) {
				//CR: TODO: Throw error here!
			}
			hasNotationAttribute = true;
		}
		attribs.addElement(a);
	} 
	//CR: TODO: Add warning if duplicate attributes are present
}

/**
 * Checks if the attribute list has an attribute 
 * of type notation.
 * 
 * @return <code>true</code> if the attribute list
 * has an attribute of type notation; <code>false</code>
 * otherwise.
 */
public boolean getHasNotationAttribute() {
	//CR: FIXME: rethink the method name
	return hasNotationAttribute;
}
/**
 * Get the list of attributes in the atribute list.
 * 
 * @return The list of attributes.
 */
public Vector getAttribs() {
	//CR: TODO: Do not return "Vector" return Collection
	return attribs;
}

/**
 * Get the name of the element to which this attribute list is associated.
 * 
 * @return The element name.
 * 
 * @see #setEleName
 */
public String getEleName() {
	return eleName;
}

/**
 * reset the attribute list.
 */
public void reset() {

	eleName = "";
	attribs = new Vector();
}

/**
 * Set the attributes for this attribute list.
 * 
 * @see #getAttribs
 * 
 * @param newAttribs The attributes.
 */
private void setAttribs(Vector newAttribs) {
	attribs = newAttribs;
}

/**
 * Merges all the attributes in the 
 * specified attribute list into this
 * attribute list.
 * 
 * @param attList The attribute list to merge from.
 */
public void merge(AttList attList) {
	
	//check if the element type is the same
	if(!attList.getEleName().equals(getEleName())) {
		//CR: TODO: Consider a warning/exception here
		return;
	}
	
	Collection newAttrs = attList.getAttribs();
	Iterator iter = newAttrs.iterator();
	
	while(iter.hasNext()) {
		addAttribute((Attribute) iter.next());
	}
}

/**
 * Set the element name associated with this attribute list.
 * 
 * @param newEleName The name of the element.
 * 
 * @see #getEleName
 */
public void setEleName(String newEleName) {
	eleName = newEleName;
}

/**
 * Convert this Attribute list to a DTD String.
 * 
 * @return java.lang.String
 */
public String toString() {
	
	//CR: TODO: Move this to a builder pattern
	String attlist = "<!ATTLIST ";
	String defaultValue;
	
	attlist += getEleName() + " \n";

	java.util.Enumeration attributes = attribs.elements();

	while( attributes.hasMoreElements() ) {
		Attribute currAttrib = (Attribute) attributes.nextElement();
		
		attlist += "\t" + currAttrib.getAttributeName() + " ";
		
		switch(currAttrib.getAttType()) {
			case AttDef.TYPE_STRING :
			case AttDef.TYPE_TOKENIZED :
				attlist += currAttrib.getDataType() + " ";
				
				String optionality = currAttrib.getOptionalityString();
				if(optionality != null)
					attlist += optionality + " ";
				
				defaultValue = currAttrib.getDefaultValue();
				if(defaultValue != null /*&& defaultValue.length() > 0*/) { //commented to take care of def value of ""
					char quote;
					if( defaultValue.indexOf('\"') == -1 )
						quote = '\"';
					else
						quote = '\'';
					attlist += quote + defaultValue + quote;
				}
				break;
					
			case Attribute.TYPE_ENUMERATION :
				attlist += "(";
				String[] enums = currAttrib.getEnumeratedValues();
				for(int i = 0; i < enums.length - 1; i++)
					attlist += enums[i] + " | ";
				attlist += enums[enums.length - 1] + ") ";

				optionality = currAttrib.getOptionalityString();
				if(optionality != null)
					attlist += optionality + " ";
				
				defaultValue = currAttrib.getDefaultValue();
				if(defaultValue != null && defaultValue.length() > 0)
					attlist += "\"" + defaultValue + "\"";
				break;
					
			case Attribute.TYPE_NOTATION :
				break;
		}
		
		attlist += "\n";
	}

	attlist += ">";
	
	return attlist;
}
}